﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Library;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Data
{
    public class BasicPerspectiveCamera : BaseCamera
    {

        public new float Fov
        {
            get { return fieldOfViewY; }
            set { fieldOfViewY = value; }
        }

        private float fieldOfViewY = 45f;
        private float fieldOfViewX = 120f;

        public float clipHither = 1f, clipYon = 120f;
        Vector dir, x, y;
        private int Width;
        private int Height;

        public BasicPerspectiveCamera(Point eye, Vector dirt, Vector up, int width, int height, float fov = 27f)
        {
            this.Height = height;
            this.Width = width;
            this.Position = eye;
            this.Target = dirt;
            this.Up = up;
            this.fieldOfViewY = fov;

            this.Update();
        }

        public void Update()
        {
            dir = (Target - (Vector)Position);
            dir = dir.Normalize();

            Vector up = Up;

            float k = MathLab.Radians(fieldOfViewY);
            x = (dir ^ up);
            x.Normalize();
            x *= Width * k / Height;

            y = (dir ^ x);
            y.Normalize();
            y *= k;
            this.CreateProjectionMatrix();
        }

        private Matrix4x4 projection;
        public void CreateProjectionMatrix()
        {
            var U = x;
            var V = y;
            var N = (Target - (Vector)Position).Normalize();
            var C = Position;
            int w = Width;
            int h = Height;
            //fieldOfViewX = fieldOfViewY*2.1f;

            var m1 = new Matrix4x4(w / 2.0f * ((float)Math.Tan(MathLab.Radians(fieldOfViewY * 0.5f))), 0, -w * 0.5f, 0,
                                   0, h / 2.0f * ((float)Math.Tan(MathLab.Radians(fieldOfViewX * 0.5f))), -h * 0.5f, 0,
                                   0, 0, 1, 0,
                                   0, 0, -1, 0);
            var m2 = new Matrix4x4(U.X, U.Y, U.Z, 0,
                                   V.X, V.Y, V.Z, 0,
                                   -N.X, -N.Y, -N.Z, 0,
                                   0, 0, 0, 1);
            var m3 = new Matrix4x4(1, 0, 0, -C.X,
                                   0, 1, 0, -C.Y,
                                   0, 0, 1, -C.Z,
                                   0, 0, 0, 1);
            this.projection = m1 * m2 * m3;
            projectTransform = new Transform(projection, Matrix4x4.Inverse(ref projection));

        }

        private Transform projectTransform;

        public override Point UnProject(ref Point v)
        {
            return projectTransform & v;
        }

        public override void GetRay(double xp, double yp, out RayData ray)
        {

            var u = (float)(2.0f * xp / Width - 1.0f);
            var v = (float)(1.0f - 2.0f * yp / Height);

            var rdir = new Vector(x.x * u + y.x * v + dir.x, x.y * u + y.y * v + dir.y, x.z * u + y.z * v + dir.z);


            var rorig = (Position + rdir * 0.1f);
            rdir.Normalize();
            ray = new RayData(ref rorig, ref rdir);
        }


        public override void Sample(ref Point p, ref Normal N, ref Normal shadeN, float u0, float u1, float u2, out RayData eyeRay,
                                   out float pdf)
        {
            var dir = p - Position;
            var dirLength = dir.Length;
            eyeRay = new RayData(this.Position, dir.Normalize(), 1e-4f, dirLength - 1e-4f);
            pdf = 1f;
        }

        public override void LookAt(Point vector, Point vector_2, Vector vector_3)
        {
            this.Position = vector;
            this.Target = (Vector)vector_2;
            this.Up = vector_3;
            this.Update();
        }

        public void GenerateRay(float xp, float yp, out RayInfo ray)
        {
            var u = 2.0f * xp / Width - 1.0f;
            var v = 1.0f - 2.0f * yp / Height;

            Vector rdir = x * u + y * v + dir;
            var rorig = (Position + rdir * 0.1f);
            rdir.Normalize();
            ray = new RayInfo(rorig, -rdir);
        }
    }
}